/**
 * @file ui.h
 * Implementation of the <b>UI<b/> module.
 * TODO show prompt to know when UI is ready
 */

#include <stdarg.h>
#include <ctype.h>
#include <strings.h>

#include <glib.h>

#include <unistd.h>

#include "common.h"
#include "ui.h"
#include "proto.h"

/* Maximum length for the buffer used for reading and writing commands */
#define MAX_BUF_LENGTH 256

typedef struct _Params Params;
struct _Params {
	Node *node;
	int nopt;
	char **options;
};

void ui_verbose(Node *node, char *format, ...);
int ui_readcmd(int sock, char *buf, int count);

int ui_help(Params *p);
int ui_status(Params *p);
int ui_llist(Params *p);
int ui_join(Params *p);
int ui_leave(Params *p);
int ui_quit(Params *p);
int ui_terminate(Params *p);
int ui_search(Params *p);
int ui_slist(Params *p);
int ui_get(Params *p);
int ui_get2(Params *p);

typedef int Cmd_fn(Params *p);

typedef struct _Cmd Cmd;
struct _Cmd {
	char *name; // name
	int nopt; // number of options required
	char *desc; // description
	Cmd_fn *fn; // function
};

static Cmd commands[] = {
	{"help", 0, "show usage", ui_help},
	{"status", 0, "show server status", ui_status},
	{"llist", 0, "list local files available for sharing", ui_llist},
	{"join", 1, "join a P2P network through node [p2paddr]", ui_join},
	{"leave", 0, "leave the P2P network", ui_leave},
	{"quit", 0, "quit the client", ui_quit},
	{"terminate", 0, "leave the P2P network and stop the server", ui_terminate},
	{"search", 1, "search file_name", ui_search},
	{"slist", 0, "list search results", ui_slist},
	{"get", 1, "get [result]", ui_get},
	{"get", 3, "get p2paddr file_name file_size", ui_get2},
	{NULL, 0, NULL, NULL}
};

int is_word_delim(char c)
{
	return isblank(c);
}

int delim(int c)
{
	return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

int ui_command(Node *node)
{
	char cmd[MAX_BUF_LENGTH];
	int tokens = 0, i;
	char **tok = NULL;
	Params p = {node, 0, NULL};

	/* Read the command from the telnet client */
	if(ui_readcmd(node->sock_ui_conn, cmd, MAX_BUF_LENGTH) == -1)
		return P2P_UI_QUIT;
	
	verbose(node, VDEBUG, "ui command: _%s_\n", cmd);

	/* Split the command into tokens */
	if((tokens = get_tokens(cmd, &tok, delim)) == 0)
		return ui_help(&p);

	/*
	for(i = 0; i < tokens; ++i)
		verbose(node, VDEBUG, "token %d: _%s_\n", i, tok[i]);
		*/

	/* Check if the command exists */
	for(i = 0; commands[i].name; ++i) if(strcasecmp(commands[i].name, tok[0]) == 0)
		break;
	if(!commands[i].name) {
		ui_verbose(node, "Unknown command: \"%s\"\n", tok[0]);
		free_tokens(tokens, tok);
		return ui_help(&p);
	}

	/* Run the command */
	p.nopt = tokens;
	p.options = tok;
	int ret = commands[i].fn(&p);
	free_tokens(tokens, tok);

	return ret;
}

void ui_verbose(Node *node, char *format, ...)
{
	char buf[MAX_BUF_LENGTH+1];
	int length;

	va_list ap;
	va_start(ap, format);
	length = vsnprintf(buf, MAX_BUF_LENGTH, format, ap);
	va_end(ap);

	if(buf[length-1] == '\n') {
		buf[length-1] = '\r';
		buf[length] = '\n';
		buf[length+1] = '\0';
		++length;
	}

	write(node->sock_ui_conn, buf, length);
}

// has the same behaviour as fgets(), ie it reads at most count-1 bytes and stores \0 at the end
int ui_readcmd(int sock, char *buf, int count)
{
	int eol = 0;
	char c;
	int length = 0;

	/* telnet uses '\r\n' as newline */
	while(eol < 2 && length < count-1) {
		if(read(sock, &c, sizeof(char)) == -1)
			return -1;
		else {
			if(c == '\r' || c == '\n')
				++eol;
			else buf[length++] = c;
		}
	}
	buf[length] = '\0';

	return length;
}

int ui_help(Params *p)
{
	ui_verbose(p->node, "Available commands:\n");
	for(int i = 0; commands[i].name; ++i)
		ui_verbose(p->node, "%11s: %s\n", commands[i].name, commands[i].desc);
	ui_verbose(p->node, "\n");
	return P2P_UI_OK;
}

int ui_status(Params *p)
{
	ui_verbose(p->node, "\n");
	ui_verbose(p->node, "\tsrv_name  = \"%s\"\n", p->node->srv_name);
	ui_verbose(p->node, "\tdir_name  = \"%s\"\n", p->node->dir_name);
	ui_verbose(p->node, "\tUI port   = %d\n", p->node->port_ui);
	ui_verbose(p->node, "\tverbosity = %d\n", p->node->verbosity);
	ui_verbose(p->node, "\tserver address  = %s:%d:%d\n",
			addr_get_ip(p->node->addr),
			addr_get_tcp_port(p->node->addr),
			addr_get_udp_port(p->node->addr));
	ui_verbose(p->node, "\tleft neighbour  = %s:%d:%d\n",
			addr_get_ip(p->node->addr_left),
			addr_get_tcp_port(p->node->addr_left),
			addr_get_udp_port(p->node->addr_left));
	ui_verbose(p->node, "\tright neighbour = %s:%d:%d\n",
			addr_get_ip(p->node->addr_right),
			addr_get_tcp_port(p->node->addr_right),
			addr_get_udp_port(p->node->addr_right));
	ui_verbose(p->node, "\n");

	return P2P_UI_OK;
}

int ui_llist(Params *p)
{
	ui_verbose(p->node, "ui_llist()\n");

	return 0;
}

int ui_join(Params *p)
{
	if(p->nopt != 2) {
		ui_verbose(p->node, "Please specify entry point address:\n");
		ui_verbose(p->node, "\tipv4:tcp_port:udp_port\n");
		return P2P_UI_ERROR;
	}

	Addr *addr = addr_set_str(NULL, p->options[1]); // entry point P2P address
	int ret = P2P_OK; // return code from proto_join

	if(addr)
		ret = proto_join(p->node, addr);
	else ui_verbose(p->node, "ui_join(): could not parse P2P address\n");

	addr_delete(&addr);

	if(ret == P2P_ERROR) {
		ui_verbose(p->node, "Join failed.\n");
		return P2P_ERROR;
	}

	return P2P_UI_OK;
}

int ui_leave(Params *p)
{
	//ui_verbose(p->node, "ui_leave()\n");
	proto_leave(p->node);

	return 0;
}

int ui_quit(Params *p)
{
	//ui_verbose(p->node, "ui_quit()\n");

	return P2P_UI_QUIT;
}

int ui_terminate(Params *p)
{
	ui_verbose(p->node, "ui_terminate()\n");

	return P2P_UI_TERMINATE;
}

int ui_search(Params *p)
{
	//ui_verbose(p->node, "ui_search()\n");
	if(p->nopt != 2) {
		ui_verbose(p->node, "Usage: search file_name\n");
		return P2P_UI_ERROR;
	}
	proto_search(p->node, p->options[1]);
	return P2P_OK;
}

int ui_slist(Params *p)
{
	//ui_verbose(p->node, "ui_slist()\n");
	GList *l;
	Result *r;
	int i;
	ui_verbose(p->node, "Search results for \"%s\":\n", p->node->search->file_name);
	if((l = p->node->search->result_list) == NULL)
		ui_verbose(p->node, "\t(no results)\n");
	for(i = 0 ; l; ++i, l = l->next) {
		r = l->data;
		ui_verbose(p->node, "\t%2d: %s (%d bytes)\n", i,
			addr_get_str(&r->node), r->file_size);
	}
	return P2P_OK;

}

int ui_get(Params *p)
{
	//ui_verbose(p->node, "ui_get()\n");
	Result *r;
	int result_id = 0;
	if(p->nopt > 2) {
		ui_verbose(p->node, "Usage: get [result]\n");
		return P2P_UI_ERROR;
	}
	if(p->nopt == 2)
		sscanf(p->options[1], "%d", &result_id);
	if(result_id < 0 || !(r = g_list_nth_data(p->node->search->result_list, result_id))) {
		ui_verbose(p->node, "Invalid result id.\n");
		ui_slist(p);
		return P2P_UI_ERROR;
	}
	return proto_get(p->node, &r->node, p->node->search->file_name, r->file_size);
}

int ui_get2(Params *p)
{
	//ui_verbose(p->node, "ui_get2()\n");
	if(p->nopt != 4) {
		ui_verbose(p->node, "Usage: get2 ip:tcp_port:udp_port file_name file_size\n");
		return P2P_UI_ERROR;
	}
	Addr *peer;
	int size;
	peer = addr_set_str(NULL, p->options[1]);
	sscanf(p->options[3], "%d", &size);
	proto_get(p->node, peer, p->options[2], size);
	addr_delete(&peer);
	return P2P_UI_OK;
}

/*============================================================================*/
/*============================================================================*/
/*============================================================================*/
/*============================================================================*/
/*============================================================================*/
/*============================================================================*/

#ifdef _aldsfjsdlk

/****************************************************/

struct params_t {
  server_params *sp;
  char *options[MAX_OPT];
};
typedef struct params_t params;

int help           (params*);
int quit           (params*);
int status         (params*);
int file_list      (params*);
int p2pjoin        (params*);
int p2pleave       (params*);
int p2phalt        (params*);

int p2psearch      (params*);
int p2plist_search (params*);
int p2plist_result (params*);
int p2pget         (params*);

/****************************************************/
/****************************************************/

struct cmd_t {
  char* name;
  int options;
  char* text;
  int (*fun)(params*);
};

static struct cmd_t commands[] = {
  { "help"    , 0, "print this message"               , help },
  { "state"   , 0, "print server state"               , status },
  { "list"    , 0, "list available files"             , file_list},
  { ""        , 0, ""                                 , NULL},
  { "join"    , 1, "connect to node [p2p_Id]"         , p2pjoin },
  { "leave"   , 0, "leave the p2p network"            , p2pleave },
  { "quit"    , 0, "detach ui from the server"        , quit },
  { "halt"    , 0, "leave the p2p and stop the server", p2phalt},
  { ""        , 0, ""                                 , NULL},
  { "search"  , 1, "search the [file]"                , p2psearch },
  { "list_search", 0, "list searches"                 , p2plist_search },
  { "list_result", 1, "list the results of search [n]", p2plist_result },
  { "get"     , 2, "get [result] from [search]"       , p2pget },
  { NULL, 0, NULL, NULL}
};

/****************************************************/
/****************************************************/

int file_list(params *p)
{
  DIR *dir;
  struct dirent* file;
  struct stat state;
  char dirname[MAX_PATH];
  char filename[MAX_PATH];

  strncpy(dirname,p->sp->dir_name,MAX_PATH);
  if (strlen(dirname) == 0)
    strncat(dirname,".",MAX_PATH);
  if (dirname[strlen(dirname) - 1] != '/')
    strncat(dirname,"/",MAX_PATH - strlen(dirname));

  if ((dir = opendir(dirname)) == NULL)
    {
      VERBOSE(p->sp,VSYSCL,"\nCannot open the shared directory \"%s\"\n",dirname);
      VERBOSE(p->sp,CLIENT,"\n\n  ** cannot open the shared directory on server ** \n\n");
      return P2P_UI_OK;
    }
  VERBOSE(p->sp,VSYSCL,"ui: getting list file for %s\n",dirname);
  VERBOSE(p->sp,CLIENT,"\nFile list\n");
  while ((file = readdir(dir)) != NULL) 
    {
      strncpy(filename,dirname,MAX_PATH);
      strncat(filename,file->d_name, MAX_PATH - strlen(filename));
      if (stat(filename,&state) == 0)
	{
	  if (S_ISREG(state.st_mode))
	    {
	      VERBOSE(p->sp,CLIENT,"  %20s  (%d bytes)\n",file->d_name,state.st_size);
	    }
	  else if (S_ISDIR(state.st_mode))
	    {
	      VERBOSE(p->sp,CLIENT,"  [dir] %14s\n",file->d_name);
	    }
	}
    }
  VERBOSE(p->sp,CLIENT,"\n");
  closedir(dir);
  return P2P_UI_OK;
}

/****************************************************/
#endif
